use anyhow::Result;
use sqlx::{Executor, Row, SqliteConnection, Statement};

use sqlx::Connection;
use nanoid::nanoid;

pub struct UrlDAO {
    db_addr: String,
}

impl UrlDAO {
    pub fn new(db_addr: String) -> Self {
        Self { db_addr }
    }

    pub async fn insert_url(&self, url: String) -> Result<(String, String)> {
        // insert url into database and return the generated id and short url
        let mut conn = SqliteConnection::connect(self.db_addr.as_str()).await?;
        conn.execute("CREATE TABLE IF NOT EXISTS urls (url TEXT PRIMARY KEY, shorten TEXT)").await?;
        let prepared = conn.prepare("INSERT INTO urls (url, shorten) VALUES (?,?)").await?;
        let shorten = nanoid!(10);
        // TODO can we remove clone here?
        let _result = prepared.query().bind(url.clone()).bind(shorten.clone()).execute(&mut conn).await?;
        let prepared = conn.prepare("SELECT * FROM urls WHERE shorten =?").await?;
        let origin: String = prepared.query().bind(shorten.clone()).fetch_one(&mut conn).await?.get("url");
        assert_eq!(origin, url);
        println!("origin: {}, shorten: {}", origin, shorten);
        Ok((url, shorten))
    }

    pub async fn get_url(&self, shorten: String) -> Result<String> {
        let mut conn = SqliteConnection::connect(self.db_addr.as_str()).await?;
        let prepared = conn.prepare("SELECT * FROM urls WHERE shorten =?").await?;
        let origin: String = prepared.query().bind(shorten.clone()).fetch_one(&mut conn).await?.get("url");
        Ok(origin)
    }
}

#[cfg(test)]
mod tests {
    use tempfile::NamedTempFile;
    use crate::dao::UrlDAO;

    #[tokio::test]
    async fn test_insert_url() {
        let temp_db = NamedTempFile::new().expect("Failed to create temp file");
        let db_addr = temp_db.path().to_str().unwrap().to_string();
        println!("db_addr: {}", db_addr);
        // Given
        let url = "https://www.example.com".to_string();

        let mock_db = UrlDAO::new(db_addr.to_string());

        let result = mock_db.insert_url(url.clone()).await;

        assert!(result.is_ok());
    }

    #[tokio::test]
    async fn test_get_url() {
        let temp_db = NamedTempFile::new().expect("Failed to create temp file");
        let db_addr = temp_db.path().to_str().unwrap().to_string();
        println!("db_addr: {}", db_addr);
        let example_url = "https://www.example.com/uuuuu".to_string();

        let mock_db = UrlDAO::new(db_addr);
        let result = mock_db.insert_url(example_url.clone()).await;
        let shorten = result.unwrap().1;

        let result = mock_db.get_url(shorten.clone()).await.unwrap();

        assert_eq!(result, example_url)
    }
}
